package oop.ex3.filemanager;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import oop.ex3.messages.SimpleMessage;
import oop.ex3.messages.ValidHeaders;
import oop.ex3.messages.exception.InvalidHeaderException;
import oop.ex3.messages.exception.UnexpectedMessageEndException;

import oop.ex3.Exception.WrongNumOfParamsException;
import oop.ex3.filemanager.actions.*;
import oop.ex3.filemanager.actions.exceptions.InvalidNumOfParamsException;
import oop.ex3.filemanager.actions.exceptions.RecievedError;
import oop.ex3.filemanager.exceptions.IllegalSourceDirException;
import oop.ex3.filemanager.exceptions.InvalidCommandException;
import oop.ex3.nameserver.exception.UnexpectedMessageException;
import oop.ex3.sockets.ClientSocket;

public class MyFileManager {
	
	//constants
	private static final int PORT_PLACE = 2;
	private static final int DIR_PLACE = 1;
	private static final int SERVERS_PLACE = 0;
	private static final int NUM_OF_PARAMS = 3;
	private static final int COMMAND_PLACE = 0;
	private static final String SPLIT_CHAR = "%";
	private static final String SPACE = " ";
	
	private static final String DIR = "DIR";
	private static final String DIR_SERVERS= "DIRSERVERS";
	private static final String ADD = "ADD";
	private static final String REMOVE = "REMOVE";
	private static final String DIR_ALL_FILES = "DIRALLFILES";
	private static final String RENAME = "RENAME";
	private static final String FIRESERVERS = "FIRESERVERS";
	private static final String QUIT = "QUIT";
	
	//TODO these should be finals. check how you do it.
	private static int _port;
	private static String _dir;
	private static FMContext _context;
	private static HashMap<String, FmAction> _userCommands ;
	private static ExecutorService _executor;
	
	//========================================================================
	//                         HELPER METHODS
	//========================================================================

	/**
	 * creates server LinkdHshSet from server file
	 * @param file - containing servers name
	 * @return servers collection
	 * @throws IOException
	 */
	private static LinkedHashSet<InetSocketAddress> 
	extractNameServers(File file) throws IOException{
		
		LinkedHashSet<InetSocketAddress> ns = new 
		LinkedHashSet<InetSocketAddress>();
		
		FileInputStream inputStream = new FileInputStream(file);
		BufferedReader buffer = new BufferedReader(
				new InputStreamReader(inputStream));
		String str;
		InetSocketAddress address;
		while ((str = buffer.readLine()) != null){
			String[] addressArray = str.split(SPLIT_CHAR);
			//TODO add thorws - NumberFormatException, IllegalArgumentException
			address = new InetSocketAddress(addressArray[0], 
					Integer.parseInt(addressArray[1]));
			ns.add(address);
		}
		return ns;	
	}
	

	/**
	 * creates user commands Array
	 */
	private static void createUserArray() {
		_userCommands = new HashMap<String,FmAction>();
		
		FireAllServers fireAllServers = new FireAllServers(_context);
		PrintAllFiles printAllFiles = new PrintAllFiles(_context);
		PrintFiles printFiles = new PrintFiles(_context);
		PrintServers printServers = new PrintServers(_context);
		RemoveFile removeFile = new RemoveFile(_context);
		RenameFile renameFile = new RenameFile(_context);
		SearchFile searchFile = new SearchFile(_context);
		
		_userCommands.put(ADD, searchFile);
		_userCommands.put(DIR, printFiles);
		_userCommands.put(DIR_ALL_FILES, printAllFiles);
		_userCommands.put(DIR_SERVERS, printServers);
		_userCommands.put(FIRESERVERS, fireAllServers);
		_userCommands.put(REMOVE, removeFile);
		_userCommands.put(RENAME, renameFile);		
	}
	
	/**
	 * notify all servers that FM is up
	 * @param ns servers collection
	 */
	private static void notifyServers(LinkedHashSet<InetSocketAddress> ns) {
		
		for (InetSocketAddress server : ns){
			try {
				ClientSocket s = new ClientSocket(new 
						InetSocketAddress(server.getAddress()
						, server.getPort()));
				FmAction action = new FmAction(_context);
				try {
					action.performHandShake(s);
					action.finishSession(s);
					s.closeClientSocket();
		
				} catch (UnexpectedMessageEndException e) {
					// TODO Auto-generated catch block
					action.finishSession(s);
				} 
				 catch (UnexpectedMessageException e) {
				action.finishSession(s);
				 } catch (RecievedError e) {
				action.finishSession(s);
				 } catch (InvalidHeaderException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			} catch (IOException e) {
				
			}
		}
		
	}

	//======================================================================
	//                          MAIN- USER THREAD
	//======================================================================
	
	/**
	 * main of MyFileManager- upload a fileManager, read servers file, 
	 * creates files collection, notify server, start listening to port
	 * and perform actions according to user commands
	 */
	public static void main(String[] args) {
		try {
			if (args.length != NUM_OF_PARAMS){
				throw new WrongNumOfParamsException();
			}
			//create fields
			_port = Integer.parseInt(args[PORT_PLACE]);
			_dir = args[DIR_PLACE];
			
			//create Context
			File nsFile = new File(args[SERVERS_PLACE]);
			LinkedHashSet<InetSocketAddress> ns = extractNameServers(nsFile);
			_context = new FMContext(ns, _dir, _port);
			notifyServers(ns);
			
			//creats userCommands Array
			createUserArray();
		
			//create Executor
			_executor = Executors.newCachedThreadPool();
			
			//start listenning to port
			_executor.execute(new FmListener(_port, _context, _executor));
			
			//start listening to user commands
			Scanner sc = new Scanner(System.in);
			String input = sc.nextLine();
			
			while (!input.equals(QUIT)){
				try {
					String[] command = input.split(SPACE);
					if (!_userCommands.containsKey(command[COMMAND_PLACE])){
						throw new InvalidCommandException();
					} else {
						_userCommands.get(command[COMMAND_PLACE]).run(command);
						input = sc.nextLine();	
					}
				} 	catch (InvalidCommandException e) {
					System.out.println("Invalid Command");
					input = sc.nextLine();
					
				} catch (InvalidNumOfParamsException e) {
					System.out.println("Invalid Command");
					input = sc.nextLine();
				}
			}

			//finish uploads and shut down
			_executor.shutdown();	

		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (WrongNumOfParamsException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalSourceDirException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
